/*
 * Copyright (c) gitee.com/jiandong001.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import buffer from '@ohos.buffer';
import systemTime from '@ohos.systemTime';
import HashMap from '@ohos.util.HashMap';
import http from '@ohos.net.http'

export abstract class ChunkListener {
  /** Invoked when a chunk of a multipart response is fully downloaded. */
  public abstract onChunkComplete(headers: HashMap<string, string>, body: string | Object | ArrayBuffer, bodyType: http.HttpDataType, isLastChunk: boolean): void | never //  throws IOException;
  /** Invoked as bytes of the current chunk are read. */
  public abstract onChunkProgress(headers: HashMap<string, string>, loaded: number, total: number): void | never // throws IOException;
}

/** Utility class to parse the body of a response of type multipart/mixed. */
export class MultipartStreamReader {
  // Standard line separator for HTTP.
  public static readonly CRLF = "\\r\\n";
  private mLastProgressEvent: number;

  public constructor(private readonly mSource: string | Object | ArrayBuffer, private readonly mSourceType: http.HttpDataType, private readonly mBoundary: string) {
  }

  /**
   * Reads all parts of the multipart response and execute the listener for each chunk received.
   *
   * @param listener Listener invoked when chunks are received.
   * @return If the read was successful
   */
  public readAllParts(listener: ChunkListener): boolean | never { //// throws IOException
    //    ByteString delimiter = ByteString.encodeUtf8(CRLF + "--" + mBoundary + CRLF);
    //    ByteString closeDelimiter = ByteString.encodeUtf8(CRLF + "--" + mBoundary + "--" + CRLF);
    //    ByteString headersDelimiter = ByteString.encodeUtf8(CRLF + CRLF);

    let delimiter: string | Uint8Array
    let closeDelimiter: string | Uint8Array
    let headersDelimiter: string | Uint8Array

    let bufferLen = 4 * 1024;
    let chunkStart = 0;
    let bytesSeen = 0;
    //    Buffer content = new Buffer();

    let content: buffer.Buffer
    let currentHeaders: HashMap<string, string> = null;
    let currentHeadersLength = 0;


    return true
//    switch (this.mSourceType) {
//      case http.HttpDataType.STRING:
//        content = buffer.from(this.mSource as string, 'utf-8') // ????????????
//        delimiter = MultipartStreamReader.CRLF + "--" + this.mBoundary + MultipartStreamReader.CRLF;
//        closeDelimiter = MultipartStreamReader.CRLF + "--" + this.mBoundary + "--" + MultipartStreamReader.CRLF;
//        headersDelimiter = MultipartStreamReader.CRLF + MultipartStreamReader.CRLF;
//
//        while (true) {
//          let isCloseDelimiter = false;
//
//          // Search only a subset of chunk that we haven't seen before + few bytes
//          // to allow for the edge case when the delimiter is cut by read call.
//          let searchStart: number = Math.max(bytesSeen - closeDelimiter.length, chunkStart);
//          let indexOfDelimiter: number = content.indexOf(delimiter, searchStart);
//          if (indexOfDelimiter == -1) {
//            isCloseDelimiter = true;
//            indexOfDelimiter = content.indexOf(closeDelimiter, searchStart);
//          }
//
//          if (indexOfDelimiter == -1) {
//            bytesSeen = content.length;
//
//            if (currentHeaders == null) {
//              let indexOfHeaders: number = content.indexOf(headersDelimiter, searchStart);
//              if (indexOfHeaders >= 0) {
////                this.mSource.read(content, indexOfHeaders);
////                content.copyto(headers, searchStart, indexOfHeaders - searchStart);
//                content = buffer.from(this.mSource as string, 'utf-8') // ????????????
//                let headers: buffer.Buffer = buffer.allocUninitializedFromPool(indexOfHeaders - searchStart);
//                content.copy(headers, 0, searchStart, indexOfHeaders);
//                currentHeadersLength = headers.length + headersDelimiter.length;
//                currentHeaders = this.parseHeaders(headers);
//              }
//            } else {
//              this.emitProgress(currentHeaders, content.length - currentHeadersLength, false, listener);
//            }
//
//            let bytesRead: number = this.mSource.read(content, bufferLen);
//            if (bytesRead <= 0) {
//              return false;
//            }
//            continue;
//          }
//
//          let chunkEnd = indexOfDelimiter;
//          let length = chunkEnd - chunkStart;
//
//          // Ignore preamble
//          if (chunkStart > 0) {
//            let chunk = new Buffer();
//            content.skip(chunkStart);
//            content.read(chunk, length);
//            this.emitProgress(currentHeaders, chunk.size() - currentHeadersLength, true, listener);
//            this.emitChunk(chunk, isCloseDelimiter, listener);
//            currentHeaders = null;
//            currentHeadersLength = 0;
//          } else {
//            content.skip(chunkEnd);
//          }
//
//          if (isCloseDelimiter) {
//            return true;
//          }
//
//          bytesSeen = chunkStart = delimiter.length;
//        }
//
//
//      break;
//      case http.HttpDataType.ARRAY_BUFFER:
//        content = buffer.from(this.mSource as ArrayBuffer)
//        //todo support ARRAY_BUFFER
//        new Error("do not support ARRAY_BUFFER for now")
//        break;
//      case http.HttpDataType.OBJECT:
//        content = buffer.from(this.mSource as Object, 0, 0)
//        //todo support OBJECT
//        new Error("do not support OBJECT for now")
//        break;
//      default:
//        break;
//    }
  }

  private parseHeaders(data: buffer.Buffer): HashMap<string, string> {
    let headers = new HashMap<string, string>();
//    let text: string = data.readUtf8();
    let text =  data.toString('utf-8')
    let lines: string[] = text.split(MultipartStreamReader.CRLF);
    for (let line in lines) {
      let indexOfSeparator: number = line.indexOf(":");
      if (indexOfSeparator == -1) {
        continue;
      }

      let key = line.substring(0, indexOfSeparator).trim();
      let value = line.substring(indexOfSeparator + 1).trim();
      headers.set(key, value);
    }

    return headers;
  }

  private emitChunk(chunk: buffer.Buffer, done: boolean, listener: ChunkListener): void | never { //// throws IOException
    //    ByteString marker = ByteString.encodeUtf8(CRLF + CRLF);
    let marker: string = MultipartStreamReader.CRLF + MultipartStreamReader.CRLF;
    let indexOfMarker = chunk.indexOf(marker);
    if (indexOfMarker == -1) {
      listener.onChunkComplete(null, chunk, this.mSourceType, done);
    } else {
      let headers = buffer.allocUninitializedFromPool(0); // new buffer
      let body = buffer.allocUninitializedFromPool(0);  // new buffer
      //--> 3
//      chunk.read(headers, indexOfMarker);
//      chunk.skip(marker.length);
//      chunk.readAll(body);
      listener.onChunkComplete(this.parseHeaders(headers), body, this.mSourceType, done);
    }
  }

  private emitProgress(headers: HashMap<string, string>, contentLength: number, isFinal: boolean, listener: ChunkListener): void | never { //// throws IOException

    if (headers == null || listener == null) {
      return;
    }

    let currentTime: number
    systemTime.getCurrentTime(true, (error, data) => {
      if (error) {
        console.error(`failed to systemTime.getCurrentTime because ` + JSON.stringify(error));
        return;
      }
      console.log(`systemTime.getCurrentTime success data : ` + JSON.stringify(data));
      currentTime = data as number

      if (currentTime - this.mLastProgressEvent > 16 || isFinal) {
        this.mLastProgressEvent = currentTime;
        let headersContentLength: number = headers.get("Content-Length") != null ? Number(headers.get("Content-Length")) : 0;
        listener.onChunkProgress(headers, contentLength, headersContentLength);
      }

    });
  }
}
